#include "context.hh"
#include "../log.hh"
#include "gl.hh"
#include "glu.hh"
#include <stdexcept>

namespace {
	struct sdl
	{
		sdl()
		{
			library::log() << "sdl::(ctor)\n";
			SDL_Init(SDL_INIT_EVERYTHING);
			library::log() << "\teverything initialized.\n";
		}
		~sdl()
		{
			library::log() << "sdl::(dtor)\n";
			SDL_Quit();
			library::log() << "\tquit.\n";
		}
	};

	static sdl _;
}

static bool key_table[128];

namespace gl
{
	context::context(dimension_type w, dimension_type h, title_cref title)
	{
		library::log() << "context::(ctor)\n";
		SDL_GL_SetAttribute(SDL_GL_RED_SIZE   ,  8);
		SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE  ,  8);
		SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE ,  8);
		SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE ,  8);
		SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE, 32);
		SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, true);
		library::log() << "\tattribute set\n";
		library::log() << "\t\topengl compatibility\n";
		library::log() << "\t\trgba8888\n";
		library::log() << "\t\t32-bit buffer\n";
		library::log() << "\t\tdoublebuffer\n";

		win = {
			SDL_CreateWindow(title.c_str(),
					SDL_WINDOWPOS_CENTERED,
					SDL_WINDOWPOS_CENTERED,
					w, h,
					SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE),
			&SDL_DestroyWindow
		};
		if (!win) throw std::runtime_error{SDL_GetError()};
		library::log() << "\twindow created\n";

		ctx = {
			SDL_GL_CreateContext(win.get()),
			&SDL_GL_DeleteContext
		};
		if (!ctx) throw std::runtime_error{SDL_GetError()};
		library::log() << "\topengl context created\n";

		library::log() << "\topengl initialization done\n"
			<< "\t\tversion : " << gl::get_string(gl::_version) << "\n"
			<< "\t\tvendor  : " << gl::get_string(gl::_vendor) << "\n"
			<< "\t\trenderer: " << gl::get_string(gl::_renderer) << "\n";

		running = true;
		ui(false);
		gl::enable(gl::_blend);
		//gl::blend_func(gl::_src_alpha, gl::_one);
		gl::blend_func(gl::_src_alpha, gl::_one_minus_src_alpha);
		gl::enable(gl::_cull_face);
	}

	void context::update(bool wait)
	{
		SDL_GL_SwapWindow(win.get());

		if (wait) SDL_WaitEvent({});

		SDL_Event e;
		while (SDL_PollEvent(&e))
			switch (e.type) {
				case SDL_QUIT:
					running = false;
					break;
				case SDL_WINDOWEVENT:
					switch (e.window.event) {
						case SDL_WINDOWEVENT_RESIZED:
							update_projection();
							break;
					}
					break;
				case SDL_KEYDOWN:
					if (e.key.keysym.sym < (int)sizeof(key_table))
						key_table[e.key.keysym.sym] = true;
					break;
				case SDL_KEYUP:
					if (e.key.keysym.sym < (int)sizeof(key_table))
						key_table[e.key.keysym.sym] = false;
					break;
			}
	}

	void context::clear(glm::vec4 const& color)
	{
		if (clear_color != color) {
			clear_color = color;
			gl::clear_color(
					clear_color.r,
					clear_color.g,
					clear_color.b,
					clear_color.a);
			library::log() << "context::clear(): color changed.\n";
		}
		gl::clear(gl::_color_buffer_bit | gl::_depth_buffer_bit);
	}

	auto context::tick() const -> millisecond_type
	{
		return SDL_GetTicks();
	}

	void context::update_projection() const
	{
		int w;
		int h;
		SDL_GL_GetDrawableSize(win.get(), &w, &h);
		float ratio = (float)w / h;

		if (ui_mode) {
			if (w > h) gl::viewport((w-h)/2, 0, h, h);
			else       gl::viewport(0, (h-w)/2, w, w);
		}
		else gl::viewport(0, 0, w, h);

		gl::matrix_mode(gl::_projection);
		gl::load_identity();

		if (ui_mode) gl::ortho(-1, 1, -1, 1, 1, -1);
		// http://en.wikipedia.org/wiki/Field_of_view_in_video_games
		else glu::perspective(fovy, ratio, 0.01, 1000);

		gl::matrix_mode(gl::_modelview);
	}

	bool context::key(key_type k) const
	{
		return key_table[k & 0x7F];
	}

	bool context::key_press(key_type k)
	{
		if (!key(k)) return false;
		key_table[k & 0x7F] = false;
		return true;
	}

	void context::ui(bool enable)
	{
		if (ui_mode == enable) return;
		ui_mode = enable;
		update_projection();
		if (enable)
			gl::disable(gl::_depth_test);
		else
			gl::enable(gl::_depth_test);
	}

	void context::fog(float start, float end, glm::vec4 const& color) const
	{
		static gl::enum_t last_mode = -1;
		static float last_start = -1;
		static float last_end   = -1;
		static glm::vec4 last_color{ 0, 0, 0, 1 };

		constexpr auto mode = gl::_linear;
		if (mode != last_mode) {
			gl::fogi(gl::_fog_mode, mode);
			last_mode = mode;
		}

		if (start != last_start) {
			gl::fogf(gl::_fog_start, start);
			last_start = start;
		}

		if (end != last_end) {
			gl::fogf(gl::_fog_end, end);
			last_end = end;
		}

		if (color != last_color) {
			gl::fogfv(gl::_fog_color, &color[0]);
			last_color = color;
		}

		fog(true);
	}

	void context::fog(bool enable) const
	{
		static bool last_enable = false;
		if (enable == last_enable) return;
		last_enable = enable;

		if (enable) gl:: enable(gl::_fog);
		else        gl::disable(gl::_fog);
	}

	void context::fov(float f)
	{
		if (f == fovy) return;
		fovy = f;
		update_projection();
	}
}

